// This file includes the code to assemble functions into an actual decoder
//	5 decoders exist in this file: 1 low-level pthreads parallel decoder, 
//	1 serial decoder, and 3 high-level openMP parallel decoders

#include "decoder.h"

// Parallel pthreads decoder:
// Precondition: Takes in m (field size), add one table, t (errors to correct), 
//		file to read encoded data from, file to write decoded data to, and flag to
//		perform timing or to skip timing
// Postcondition: Decoded words are written to an output file and timing is displayed
//		(or not depending on timeFlag)
void pthreadsDecoder(int m, Matrix *add_one, int t, char *encoded, char *decoded, int timeFlag){
   // Timing variables
   struct timespec start, finish;
   double time_ns;
   // File pointers
   FILE *filePtr1, *filePtr2;
   // Decoding variables
   Vector **codeArray, **resultVector;
   int codeLen, numWords, fileData, msgLen;
   int i, j;

   // Length of each code word and message words
   codeLen = (1<<m)-1;
   msgLen = (1<<m)-1-2*t;

   // Open encoded message file for reading
   filePtr1 = fopen(encoded,"r");
   // Open decoded message file for writing
   filePtr2 = fopen(decoded,"w");
  
   // Check if files opened properly
   if((filePtr1 == NULL)||(filePtr2 == NULL)){
     fprintf(stderr, "ERROR opening file!\n");
	 // If one opened properly close it
     if(filePtr1 != NULL)
       fclose(filePtr1);
     else if (filePtr2 != NULL)
       fclose(filePtr2);
   }
   else{ 
      // Get the number of code words in the file
      numWords = fileLength(filePtr1, codeLen);

      // Create an array of code words from the file
      codeArray = (Vector**)malloc(sizeof(Vector*)*numWords);

      // Loop to fill the codeArray
      for(i=0;i<numWords; i++){
         codeArray[i]=allocate_vector(codeLen); // Create a new code word
         for(j=0;j<codeLen;j++){
           fscanf(filePtr1, "%d", &fileData); // Get data for code word
           codeArray[i]->data[j] = fileData;  // Store data in code word
         }
      }

      // Close encoded message file
      fclose(filePtr1);
      
      // Get start time for timing
      clock_gettime(CLOCK_REALTIME, &start);

      // Send words to parallelTowers (pipelined decoder)
      resultVector = parallelTowers(m, add_one, t, numWords, codeArray);

      // Extract message word from decoded code words
      for(i=0;i<numWords;i++){
        free_vector(codeArray[i]);				// Free original code word
        codeArray[i]=allocate_vector(msgLen);	// Allocate vector of length message word
        for(j=0;j<msgLen;j++){
          codeArray[i]->data[j] = resultVector[i]->data[codeLen-1-j]; // Get message
        }
        free_vector(resultVector[i]);			// Free decoded code word
      }
      free(resultVector);	// Free result vector

      // Get end time
      clock_gettime(CLOCK_REALTIME, &finish);
	  // Get time difference
      time_ns = TIME_DIF_TO_NS(start, finish);
	  // Display time if timeFlag set
	  if (timeFlag){
        printf("%lf\n",time_ns);
	  }
      
      // Write codeArray to file
      for(i=0;i<numWords; i++){
        for(j=0;j<msgLen;j++){
          // Write Message Word to file
          fprintf(filePtr2,"%d\n",codeArray[i]->data[j]);
        }
        // Free message vector
        free_vector(codeArray[i]);
      }
      
      // Free codeArray
      free(codeArray);
     
      // Close decoded message
      fclose(filePtr2);
   }
   pthread_exit(NULL);
}

// Serial Decoder:
// Precondition: Takes in m (field size), add one table, t (errors to correct), 
//		file to read encoded data from, file to write decoded data to, and flag to
//		perform timing or to skip timing
// Postcondition: Decoded words are written to an output file and timing is displayed
//		(or not depending on timeFlag)
void serialDecoder(int m, Matrix *add_one, int t, char *encoded, char *decoded, int timeFlag){
   // Time variables
   struct timespec start, finish;
   double time_ns;
   // File variables
   FILE *filePtr1, *filePtr2;
   // Decoder variables
   Vector **codeArray, *result;
   int codeLen, numWords, fileData, msgLen;
   int i, j;

   // Length of each code word and message word
   codeLen = (1<<m)-1;
   msgLen = (1<<m)-1-2*t;

   // Open encoded message file for reading
   filePtr1 = fopen(encoded,"r");
   // Open decoded message file for writing
   filePtr2 = fopen(decoded,"w");
  
   // Check if both files opened properly
   if((filePtr1 == NULL)||(filePtr2 == NULL)){
     fprintf(stderr, "ERROR opening file!\n");
	 // If one file opened, close it
     if(filePtr1 != NULL)
       fclose(filePtr1);
     else if (filePtr2 != NULL)
       fclose(filePtr2);
   }
   else{ 
      // Get the number of words in the file
      numWords = fileLength(filePtr1, codeLen);

      // Create an array of code words from the file
      codeArray = (Vector**)malloc(sizeof(Vector*)*numWords);

      // Loop to fill the codeArray
      for(i=0;i<numWords; i++){
         codeArray[i]=allocate_vector(codeLen); // Create a new code word
         for(j=0;j<codeLen;j++){
           fscanf(filePtr1, "%d", &fileData); // Get data for code word
           codeArray[i]->data[j] = fileData;  // Store data in code word
         }
      }

      // Close encoded data file
      fclose(filePtr1);

      // Get start time
      clock_gettime(CLOCK_REALTIME, &start);

      // Loop through decoding words 
      for(i=0;i<numWords;i++){
        // Send words to euclid (function to decode a code word)
        result = euclid(m, add_one, t, codeArray[i]);
        
		// Free original code word vector
        free_vector(codeArray[i]);
		// Allocate a vector for message word
        codeArray[i]=allocate_vector(msgLen);
		// Extract message word from decoded code word
        for(j=0;j<msgLen;j++){
          codeArray[i]->data[j] = result->data[codeLen-1-j];
        }
		// Free decoded code word
        free_vector(result);
      }

      // Get end time
      clock_gettime(CLOCK_REALTIME, &finish);
	  // Get time difference
      time_ns = TIME_DIF_TO_NS(start, finish);
	  // Display time if timeFlag is set
	  if(timeFlag){
        printf("%lf\n",time_ns);
	  }
      
      // Write codeArray to file
      for(i=0;i<numWords; i++){
        for(j=0;j<msgLen;j++){
          // Write Message Word to file
          fprintf(filePtr2,"%d\n",codeArray[i]->data[j]);
        }
        // Free message vector
        free_vector(codeArray[i]);
      }
      
      // Free codeArray
      free(codeArray);
     
      // Close decoded word file
      fclose(filePtr2);
   }
}

// Parallel Decoder - openMP dynamic
// Precondition: Takes in m (field size), add one table, t (errors to correct), 
//		p (number of processing units), file to read encoded data from, file 
//		to write decoded data to, and flag to perform timing or to skip timing
// Postcondition: Decoded words are written to an output file and timing is displayed
//		(or not depending on timeFlag)
void openMPDynamicDecoder(int m, Matrix *add_one, int t, int p, char *encoded, char *decoded, int timeFlag){
   // Time variables
   struct timespec start, finish;
   double time_ns;
   // File variables
   FILE *filePtr1, *filePtr2;
   // Decoder variables
   Vector **codeArray, *result;
   int codeLen, numWords, fileData, msgLen;
   int i, j;

   // Length of each code word and message word
   codeLen = (1<<m)-1;
   msgLen = (1<<m)-1-2*t;

   // Open encoded message file for reading
   filePtr1 = fopen(encoded,"r");
   // Open decoded message file for writing
   filePtr2 = fopen(decoded,"w");
  
   // Check for null file pointers
   if((filePtr1 == NULL)||(filePtr2 == NULL)){
     fprintf(stderr, "ERROR opening file!\n");
	 // If one file opened properly close it
     if(filePtr1 != NULL)
       fclose(filePtr1);
     else if (filePtr2 != NULL)
       fclose(filePtr2);
   }
   else{ 
      // Get the number of words in the file
      numWords = fileLength(filePtr1, codeLen);

      // Create an array of code words from the file
      codeArray = (Vector**)malloc(sizeof(Vector*)*numWords);

      // Loop to fill the codeArray
      for(i=0;i<numWords; i++){
         codeArray[i]=allocate_vector(codeLen); // Create a new code word
         for(j=0;j<codeLen;j++){
           fscanf(filePtr1, "%d", &fileData); // Get data for code word
           codeArray[i]->data[j] = fileData;  // Store data in code word
         }
      }

      // Close encoded word file
      fclose(filePtr1);
      
      // Get start time
      clock_gettime(CLOCK_REALTIME, &start);

      // Decode words in parallel using p threads
      #pragma omp parallel num_threads(p) private(i, j, result)
      {
		 // Use dynamic scheduling
         #pragma omp for schedule(dynamic)
         for(i=0;i<numWords;i++){
            // Send words to euclid (decoder function)
            result = euclid(m, add_one, t, codeArray[i]);

            // Free original code word
            free_vector(codeArray[i]);
			// Create a vector to hold message word
            codeArray[i]=allocate_vector(msgLen);
			// Extract message word from decoded code word
            for(j=0;j<msgLen;j++){
              codeArray[i]->data[j] = result->data[codeLen-1-j];
            }
			// Free decoded code word
            free_vector(result);
          }
      }
      // Get end time
      clock_gettime(CLOCK_REALTIME, &finish);
	  // Get time difference
      time_ns = TIME_DIF_TO_NS(start, finish);
	  // Display time if flag is set
	  if(timeFlag){
        printf("%lf\n",time_ns);
	  }
      
      // Write codeArray to file
      for(i=0;i<numWords; i++){
        for(j=0;j<msgLen;j++){
          // Write Message Word to file
          fprintf(filePtr2,"%d\n",codeArray[i]->data[j]);
        }
        // Free message vector
        free_vector(codeArray[i]);
      }
      
      // Free codeArray
      free(codeArray);
     
      // Close decoded word file
      fclose(filePtr2);
   }
}

// Parallel Decoder - openMP static
// Precondition: Takes in m (field size), add one table, t (errors to correct), 
//		p (number of processing units), file to read encoded data from, file 
//		to write decoded data to, and flag to perform timing or to skip timing
// Postcondition: Decoded words are written to an output file and timing is displayed
//		(or not depending on timeFlag)
void openMPStaticDecoder(int m, Matrix *add_one, int t, int p, char *encoded, char *decoded, int timeFlag){
   // Time variables
   struct timespec start, finish;
   double time_ns;
   // File variables
   FILE *filePtr1, *filePtr2;
   // Decoder variables
   Vector **codeArray, *result;
   int codeLen, numWords, fileData, msgLen;
   int i, j;

   // Length of each code word and message word
   codeLen = (1<<m)-1;
   msgLen = (1<<m)-1-2*t;

   // Open encoded message file for reading
   filePtr1 = fopen(encoded,"r");
   // Open decoded message file for writing
   filePtr2 = fopen(decoded,"w");
  
   // Check for null file pointers
   if((filePtr1 == NULL)||(filePtr2 == NULL)){
     fprintf(stderr, "ERROR opening file!\n");
	 // If one file opened properly close it
     if(filePtr1 != NULL)
       fclose(filePtr1);
     else if (filePtr2 != NULL)
       fclose(filePtr2);
   }
   else{ 
      // Get the number of words in the file
      numWords = fileLength(filePtr1, codeLen);

      // Create an array of code words from the file
      codeArray = (Vector**)malloc(sizeof(Vector*)*numWords);

      // Loop to fill the codeArray
      for(i=0;i<numWords; i++){
         codeArray[i]=allocate_vector(codeLen); // Create a new code word
         for(j=0;j<codeLen;j++){
           fscanf(filePtr1, "%d", &fileData); // Get data for code word
           codeArray[i]->data[j] = fileData;  // Store data in code word
         }
      }

      // Close encoded word file
      fclose(filePtr1);
      
      // Get start time
      clock_gettime(CLOCK_REALTIME, &start);
      // Decode words in parallel using p threads
      #pragma omp parallel num_threads(p) private(i, j, result)
      {
	     // Use static scheduling for decode
         #pragma omp for schedule(static)
         for(i=0;i<numWords;i++){
            // Send words to euclid
            result = euclid(m, add_one, t, codeArray[i]);

            // Free original code word vector
            free_vector(codeArray[i]);
			// Create new vector to hold message word
            codeArray[i]=allocate_vector(msgLen);
			// Extract message word from decoded code word
            for(j=0;j<msgLen;j++){
              codeArray[i]->data[j] = result->data[codeLen-1-j];
            }
			// Free decoded code word
            free_vector(result);
          }
      }
      // Get end time
      clock_gettime(CLOCK_REALTIME, &finish);
	  // Get time difference
      time_ns = TIME_DIF_TO_NS(start, finish);
	  // Display time if timeFlag is set
	  if(timeFlag){
        printf("%lf\n",time_ns);
	  }
      
      // Write codeArray to file
      for(i=0;i<numWords; i++){
        for(j=0;j<msgLen;j++){
          // Write Message Word to file
          fprintf(filePtr2,"%d\n",codeArray[i]->data[j]);
        }
        // Free message vector
        free_vector(codeArray[i]);
      }
      
      // Free codeArray
      free(codeArray);
     
      // Close decoded word file
      fclose(filePtr2);
   }
}

// Parallel Decoder - openMP guided
// Precondition: Takes in m (field size), add one table, t (errors to correct), 
//		p (number of processing units), file to read encoded data from, file 
//		to write decoded data to, and flag to perform timing or to skip timing
// Postcondition: Decoded words are written to an output file and timing is displayed
//		(or not depending on timeFlag)
void openMPGuidedDecoder(int m, Matrix *add_one, int t, int p, char *encoded, char *decoded, int timeFlag){
   // Time variables
   struct timespec start, finish;
   double time_ns;
   // File variables
   FILE *filePtr1, *filePtr2;
   // Decoder variables
   Vector **codeArray, *result;
   int codeLen, numWords, fileData, msgLen;
   int i, j;

   // Length of each code word and message word
   codeLen = (1<<m)-1;
   msgLen = (1<<m)-1-2*t;

   // Open encoded message file for reading
   filePtr1 = fopen(encoded,"r");
   // Open decoded message file for writing
   filePtr2 = fopen(decoded,"w");
  
   // Check for null file pointers
   if((filePtr1 == NULL)||(filePtr2 == NULL)){
     fprintf(stderr, "ERROR opening file!\n");
	 // If a file opened correctly, close it
     if(filePtr1 != NULL)
       fclose(filePtr1);
     else if (filePtr2 != NULL)
       fclose(filePtr2);
   }
   else{ 
      // Get the number of words in the file
      numWords = fileLength(filePtr1, codeLen);

      // Create an array of code words from the file
      codeArray = (Vector**)malloc(sizeof(Vector*)*numWords);

      // Loop to fill the codeArray
      for(i=0;i<numWords; i++){
         codeArray[i]=allocate_vector(codeLen); // Create a new code word
         for(j=0;j<codeLen;j++){
           fscanf(filePtr1, "%d", &fileData); // Get data for code word
           codeArray[i]->data[j] = fileData;  // Store data in code word
         }
      }

      // Close encoded word file
      fclose(filePtr1);
      
      // Get start time
      clock_gettime(CLOCK_REALTIME, &start);
      // Decode words in parallel using p threads
      #pragma omp parallel num_threads(p) private(i, j, result)
      {
		 // Decode using guided task distribution
         #pragma omp for schedule(guided)
         for(i=0;i<numWords;i++){
            // Send words to euclid
            result = euclid(m, add_one, t, codeArray[i]);

            // Free original code word
            free_vector(codeArray[i]);
			// Create a vector to hold message word
            codeArray[i]=allocate_vector(msgLen);
			// Extract message word from decoded code word
            for(j=0;j<msgLen;j++){
              codeArray[i]->data[j] = result->data[codeLen-1-j];
            }
			// Free decoded code word
            free_vector(result);
          }
      }

      // Get end time
      clock_gettime(CLOCK_REALTIME, &finish);
	  // Get time difference
      time_ns = TIME_DIF_TO_NS(start, finish);
	  // Display time if timeFlag is set
	  if(timeFlag){
        printf("%lf\n",time_ns);
	  }
      
      // Write codeArray to file
      for(i=0;i<numWords; i++){
        for(j=0;j<msgLen;j++){
          // Write Message Word to file
          fprintf(filePtr2,"%d\n",codeArray[i]->data[j]);
        }
        // Free message vector
        free_vector(codeArray[i]);
      }
      
      // Free codeArray
      free(codeArray);
     
      // Close decoded word file
      fclose(filePtr2);
   }
}

// Code Word Counter:
// Precondition: filePtr points to a valid file (for reading)
// Postcondition: Returns the number of code words in the file
int fileLength(FILE *filePtr, int codeLen){
   // Variables to perform counting
   int data, count;

   // Initialize counter to 0
   count = 0;

   // Loop until end of file
   while(!feof(filePtr)){
      fscanf(filePtr, "%d", &data); // Increment file pointer
      count++; 			    // Increment count
   }
  
   // Return filePtr to beginning of file
   rewind(filePtr); 
 
   // Return number of code words in the file
   return(count/codeLen);  
}
